'use client'; import { useEffect, useRef, useState } from 'react'; import { DonationAlertData, DonationAlertConfig } from '@/types/donation'; import './style.scss'; type Props = { alert: DonationAlertData; config: DonationAlertConfig; isAudioOnly: boolean; isVideoOnly: boolean; onComplete: () => void; }; /** * config.message 템플릿의 {이름}, {금액}을 실제 값으로 치환하여 JSX 반환. * 각 변수에 sender-name / sender-amount 클래스를 적용해 폰트 스타일 유지. */ function resolveTemplate(template: string, sendName: string, amount: number): React.ReactNode { const formattedAmount = `${amount.toLocaleString()}원`; const parts = template.split(/(\{이름\}|\{금액\})/g); return parts.map((part, i) => { if (part === '{이름}') { return {sendName}; } if (part === '{금액}') { return {formattedAmount}; } return {part}; }); } export default function View({ alert, config, isAudioOnly, isVideoOnly, onComplete }: Props) { const [phase, setPhase] = useState<'delay'|'enter'|'show'|'exit'>('delay'); const audioRef = useRef(null); const timerRef = useRef(null); useEffect(() => { const delayMs = (config.playDelaySec || 0) * 1000; timerRef.current = setTimeout(() => { setPhase('enter'); // 사운드 재생 if (config.enableSound && config.soundUrl && !isVideoOnly) { const audio = new Audio(config.soundUrl); audioRef.current = audio; audio.play().catch(() => {}); } // enter → show (Animate.css 기본 1초) setTimeout(() => { setPhase('show'); timerRef.current = setTimeout(() => { setPhase('exit'); setTimeout(() => { onComplete(); }, 500); }, (config.displayDurationSec || 10) * 1000); }, 1000); }, delayMs); return () => { if (timerRef.current) { clearTimeout(timerRef.current); } if (audioRef.current) { audioRef.current.pause(); audioRef.current = null; } }; }, []); if (phase === 'delay') { return null; } // Animate.css 클래스 const popupAnim = config.popupEffect || 'fadeIn'; const textAnim = config.textEffect ? `animate__animated animate__infinite animate__${config.textEffect}` : ''; const containerClass = phase === 'enter' ? `donation-alert animate__animated animate__${popupAnim}` : phase === 'exit' ? 'donation-alert animate__animated animate__fadeOut' : 'donation-alert alert-show'; // CSS custom properties const cssVars = { '--nickname-font-family': config.nicknameFontFamily || 'inherit', '--nickname-font-size': `${config.nicknameFontSize}px`, '--nickname-font-color': config.nicknameFontColor || '#FFD700', '--amount-font-family': config.amountFontFamily || 'inherit', '--amount-font-size': `${config.amountFontSize}px`, '--amount-font-color': config.amountFontColor || '#FF6B35', '--message-font-family': config.messageFontFamily || 'inherit', '--message-font-size': `${config.messageFontSize}px`, '--message-font-color': config.messageFontColor || '#FFFFFF', '--template-font-family': config.templateFontFamily || 'inherit', '--template-font-size': `${config.templateFontSize}px`, '--template-font-color': config.templateFontColor || '#FFFFFF', } as React.CSSProperties; return (
{/* 이미지 */} {!isAudioOnly && config.enableImage && config.imageUrl && (
donation
)} {/* 후원 정보 */} {!isAudioOnly && (
{/* 알림 메시지 (config.message 템플릿 치환) */}
{config.message ? resolveTemplate(config.message, alert.sendName, alert.amount) : (<> {alert.sendName} {alert.amount.toLocaleString()}원 ) }
{/* 후원자 전달 내용 */} {alert.message && (
{alert.message}
)}
)}
); }